/*
 * SistemaDeTipos.java
 * Clase que realiza la revision de tipos y llama a los metodos necesarios
 * dependiendo de que operacion se quiera realizar.
 * Ana Silvia Gonzales Torres 10194
 * Diego Alejandro Enriquez Rodriguez 10038
 */
package gramatica;

import arbol.Nodo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;

/**
 *
 * @author Ana Silvia Gonzáles
 * @author Diego Enríquez
 */
public class SistemaDeTipos {
    /**
     * La raiz del arbol a analizar.
     */
    private Nodo root;
    /**
     * El GestorBaseDeDatos a utilizar.
     */
    private gestor.base.de.datos.GestorBaseDeDatos gestor;
    
     /**
      * Crea un objeto SistemaDeTipos con la raiz especificada
      * 
      * @param root La raiz del arbol sintactico a analizar
      */
    public SistemaDeTipos(Nodo root){
        this.root = root;
        gestor = main.Main.getGestor();
    }
    
    /**
     * Revisa el arbol sintactico y ejecuta acciones semanticas
     */
    public void rev(){
        for (int i = 0 ; i != root.getChildCount(); i++) {
            revRaiz(root.getChildAt(i));
        }
    }
    
    /**
     * Revisa cada statement y los separa entre DDL y DML
     * 
     * @param nodo El nodo a revisar (Debe ser un DDL o un DML)
     */
    private void revRaiz(Nodo nodo){
        if (nodo.toString().equals("DDL")){
            revDDL(nodo);
        }
        else{
            revDML(nodo);
        }
    }
    
    /**
     * Revisa un nodo DDL y llama al metodo necesario, dependiendo la instruccion
     * 
     * @param nodo El nodo a revisar (Debe ser un DDL)
     */
    private void revDDL(Nodo nodo){
        switch (nodo.getFirstChild().toString()) {
            case "CREATE":
                revCreate(nodo.getFirstChild());
                break;
            case "ALTER":
                revAlter(nodo.getFirstChild());
                break;
            case "DROP":
                revDrop(nodo.getFirstChild());
                break;
            case "USE":
                revUse(nodo.getFirstChild());
                break;
            case "SHOW":
                revShow(nodo.getFirstChild());
                break;
        }
    }
    
    /**
     * Revisa un nodo DML y llama al metodo necesario, dependiendo la instruccion
     * 
     * @param nodo El nodo a revisar (Debe ser un DML)
     */
    private void revDML(Nodo nodo){
        switch (nodo.getFirstChild().toString()) {
            case "INSERT":
                revInsert(nodo.getFirstChild());
                break;
            case "UPDATE":
                revUpdate(nodo.getFirstChild());
                break;
            case "DELETE":
                revDelete(nodo.getFirstChild());
                break;
            case "SELECT":
                revSelect(nodo.getFirstChild());
                break;
        }
        if (!nodo.getFirstChild().toString().equals("SELECT") && gestor.getCurrent() != null){
            gestor.saveDataBase(gestor.getCurrent().getName());
        }
    }
    
    /**
     * Revisa un nodo Select y llama a la instruccion para realizarlo.
     * 
     * Tambien se encarga de la revision de sus hijos.
     * 
     * @param nodo El nodo a revisar (Debe ser un SELECT)
     * @return Devuelve true si se realizo el select con exito, false de lo contrario
     */
    private boolean revSelect(Nodo nodo){
        boolean res = false;
        Nodo idlist = nodo.getChildAt(1);
        ArrayList<String> columns;
        if (idlist.toString().equals("*")){
            columns = new ArrayList<>();
            columns.add("*");
        }
        else{
            columns = revIdList(idlist);
        }
        ArrayList<String> tables;
        tables = revIdList(nodo.getChildAt(3));
        Stack<String>[] where = null;
        if (nodo.getChildCount() == 5 || nodo.getChildCount() == 8){
            where = revWhere(nodo.getChildAt(4));
            if (where == null){
                return false;
            }
        }
        Stack<String>[] order = null;
        if (nodo.getChildCount() == 7 || nodo.getChildCount() == 8){
            order = revOrder(nodo.getLastChild(), columns);
            if (order == null){
                return false;
            }
        }
        // LLAMAR AL METODO SELECT
        try{
            mensaje(gestor.select(columns, tables, where, order).showTable());
        }
        catch(NullPointerException ex){
            
        }
        return res;
    }
    
    /**
     * Revisa un nodo Order y los nombres de las columnas que se especifican
     * en el mismo.
     * 
     * @param nodo El nodo a revisar (Debe ser un ORDER).
     * @param columns Las columnas que fueron referenciadas en el Select.
     * @return Devuelve un Array con dos stacks, el primero contiene los nombres
     * de las columnas a ordenar y el segundo si se ordenaran asendente o desendentemente.
     */
    private Stack<String>[] revOrder(Nodo nodo, ArrayList<String> columns){
        Stack<String>[] ret = new Stack[2];
        ret[0] = new Stack<String>();
        ret[1] = new Stack<String>();
        while (nodo.toString().equals("ORDERS")){
            Nodo order = nodo.getFirstChild();
            String varName = getVarName(order.getFirstChild());
            ret[0].add(varName);
            if (!columns.contains(varName) && !columns.contains("*")){
                error("La columna especificada debe estar en el select", order);
                return null;
            }
            if (order.getChildCount() == 2){
                ret[1].push(order.getLastChild().toString());
            }
            else{
                ret[1].push("ASC");
            }
            nodo = nodo.getLastChild();
        }
        Collections.reverse(ret[0]);
        Collections.reverse(ret[1]);
        return ret;
    }
     /**
      * Revisa un nodo IdList
      * 
      * @param nodo El nodo a revisar (Debe ser un IDLIST).
      * @return Devuelve un ArrayList de Strings con los ids
      * que se indicaron en forma de lista
      */
    private ArrayList<String> revIdList(Nodo nodo){
        ArrayList<String> res = new ArrayList<>();
        while (nodo.toString().equals("IDLIST")){
            res.add(getVarName(nodo.getFirstChild()));
            nodo = nodo.getLastChild();
        }
        return res;
    }
    
    /**
     * Revisa un nodo Delete
     * 
     * @param nodo El nodo a revisar (Debe ser un DELETE).
     * @return Devuelve true si se realizo el delete con exito, false de lo contrario
     */
    private boolean revDelete(Nodo nodo){
        boolean res = false;
        String id = getVarName(nodo.getChildAt(2));
        if(revTabla(nodo, id)){
            Stack<String>[] where = null;
            if (nodo.getChildCount() == 4){
                where = revWhere(nodo.getLastChild());
                if (where == null){
                    return false;
                }
            }
            // Llamar al Delete
            gestor.deleteRegistro(id, where);
            res = true;
        }
        return res;
    }
    
    /**
     * Revisa un nodo Update
     * 
     * @param nodo El nodo a revisar (Debe ser un UPDATE).
     * @return Devuelve true si se realizo el update con exito, falase de lo contrario.
     */
    private boolean revUpdate(Nodo nodo){
        boolean res = false;
        String id = getVarName(nodo.getChildAt(1));
        ArrayList<String>[] valores = null;
        ArrayList<String>[] valoresO = new ArrayList[2];
        valoresO[0] = new ArrayList<>();
        valoresO[1] = new ArrayList<>();
        if (revTabla(nodo, id)){
            valores = revUpdates(nodo.getChildAt(3), id);
            if (valores != null){
                ArrayList<Integer> orden = new ArrayList<Integer>();
                for (String i : valores[0] ){
                    orden.add(gestor.getCurrent().getTable(id).getColNum(i));
                    valoresO[0].add("");
                    valoresO[1].add("");
                }
                orden = mergeSort(orden);
                for (int i : orden){
                    String tableId = gestor.getCurrent().getTable(id).getColName(i);
                    int posO = valores[0].indexOf(tableId);
                    int posV = orden.indexOf(i);
                    valoresO[0].remove(posV);
                    valoresO[1].remove(posV);
                    if (posV < valoresO[0].size()){
                        valoresO[0].add(posV, valores[0].get(posO));
                        valoresO[1].add(posV, valores[1].get(posO));
                    }
                    else{
                        valoresO[0].add(valores[0].get(posO));
                        valoresO[1].add(valores[1].get(posO));
                    }
                }
            }
            Stack<String>[] where = null;
            if (nodo.getChildCount() == 5) {
                where = revWhere(nodo.getLastChild());
                if (where == null){
                    return false;
                }
            }
            // LLAMADA AL UPDATE
            if (valores != null){
                gestor.update(id, valoresO[1], valoresO[0], where);
                res = true;
            }
        }
        return res;
    }
    
    /**
     * Realiza un merge sort del ArrayList orden.
     * 
     * Este merge sort se realiza para ordenar los elementos del update,
     * que pueden llegar en desorden.
     * 
     * @param orden El ArrayList a ordenar.
     * @return Devuelve el ArrayList ya ordenado.
     */
    private ArrayList<Integer> mergeSort(ArrayList<Integer> orden){
        if (orden.size() <= 1){
            return orden;
        }
        ArrayList<Integer> left, right;
        left = new ArrayList<>();
        right = new ArrayList<>();
        int middle = orden.size() / 2;
        for (int i = 0; i < orden.size(); i++){
            int element = orden.get(i);
            if (i < middle){
                left.add(element);
            }
            else{
                right.add(element);
            }
        }
        left = mergeSort(left);
        right = mergeSort(right);
        return merge(left, right);
    }
    
    /**
     * Metodo que se utiliza dentro del merge sort.
     * 
     * Permite unir dos ArrayList y ordenarlos.
     * 
     * @param left el lado derecho del ArrayList a ordenar.
     * @param right el lado izquierdo del ArrayList a ordenar.
     * @return Devuelve un ArrayList ordenado, que es la union de los dos parametros.
     */
    private ArrayList<Integer> merge(ArrayList<Integer> left, ArrayList<Integer> right){
        ArrayList<Integer> result = new ArrayList<>();
        while (left.size() > 0 || right.size() > 0){
            if (left.size() > 0 && right.size() > 0){
                if (left.get(0) <= right.get(0)){
                    result.add(left.get(0));
                    left.remove(0);
                }
                else{
                    result.add(right.get(0));
                    right.remove(0);
                }
            }
            else if (left.size() > 0){
                result.add(left.get(0));
                left.remove(0);
            }
            else if (right.size() > 0){
                result.add(right.get(0));
                right.remove(0);
            }
        }
        return result;
    }
    
    /**
     * Revisa un Where.
     * 
     * @param nodo El nodo a revisar (Debe ser un WHERE).
     * @return Devuelve un Array de Stacks cuyo primer elemento son los operandos
     * y su segundo elemento son los tipos de dichos operandos.
     */
    private Stack<String>[] revWhere(Nodo nodo){
        List<String>[] exp = revExp(nodo.getLastChild());
        Stack<String>[] res = new Stack[2];
        Stack<String> ops = new Stack<>();
        Stack<String> tipos = new Stack<>();
        Collections.reverse(exp[0]);
        Collections.reverse(exp[1]);
        ops.addAll(exp[0]);
        tipos.addAll(exp[1]);
        res[0] = (Stack)ops.clone();
        res[1] = (Stack)tipos.clone();
        Stack<String> simO = new Stack<>();
        Stack<String> simR = new Stack<>();
        try{
            while (!ops.empty()){
                String tipo1 = tipos.pop();
                String op1 = ops.pop();
                if (tipo1.equals("OTRO") || tipo1.equals("BOOLEAN")){
                    if (tipo1.equals("BOOLEAN")){
                        simO.pop();
                        String tipo2 = simR.pop();
                        String tipo3 = "";
                        if (!op1.equals("NOT")){
                            simO.pop();
                            tipo3 = simR.pop();
                        }
                        if (tipo2.equals("BOOLEAN") && (tipo3.equals("BOOLEAN") || tipo3.equals(""))){
                            simO.push("FANTASMA");
                            simR.push("BOOLEAN");
                        }
                        else{
                            error("Operacion Invalida", nodo);
                            return null;
                        }
                    }
                    else{
                        simO.pop();
                        simO.pop();
                        simR.pop();
                        simR.pop();
                        simO.push("FANTASMA");
                        simR.push("BOOLEAN");
                    }
                }
                else{
                    simO.push(op1);
                    simR.push(tipo1);
                }
            }
        }
        catch (Exception ex){
            error ("Operandos invalidos", nodo);
            return null;
        }
        return res;
    }
    
    /**
     * Revisa un nodo Updates.
     * 
     * @param nodo El nodo a revisar (Debe ser un UPDATES).
     * @param tabla La tabla que se va a updatear.
     * @return Devuelve un Array de ArrayList, cuyo primer elemento contiene
     * a las columnas que van a ser updateadas y su segundo elemento contiene
     * el valor que se le asiganara a dicha columna.
     */
    private ArrayList<String>[] revUpdates(Nodo nodo, String tabla){
        ArrayList<String>[] ret = new ArrayList[2];
        ret[0] = new ArrayList<>();
        ret[1] = new ArrayList<>();
        while (nodo.toString().equals("UPDATES") && ret != null){
            ret = revUpdat(nodo.getFirstChild(), tabla, ret);
            nodo = nodo.getLastChild();
        }
        return ret;
    }
    
    /**
     * Revisa un nodo Updat.
     * 
     * @param nodo El nodo a revisar (Debe ser un Updat)
     * @param tabla La tabla que se va a updatear
     * @param ret El Stack de ArrayList que sera devuelto
     * @return Devuelve un Array de ArrayList, cuyo primer elemento contiene
     * a las columnas que van a ser updateadas y su segundo elemento contiene
     * el valor que se le asiganara a dicha columna.
     */
    private ArrayList<String>[] revUpdat(Nodo nodo, String tabla, ArrayList<String>[] ret){
        String columna = getVarName(nodo.getFirstChild());
        if (this.revColumn(nodo, tabla, columna, true) && ret != null){
            String tipoCol = gestor.getCurrent().getTable(tabla).getTipoCol(columna);
            String tipoVal;
            if(nodo.getChildAt(2).toString().equals("VALUE")){
                tipoVal = getValueType(nodo.getChildAt(2));
            }
            else{
                tipoVal = tipoCol;
            }
            if (tipoCol.equals(tipoVal)){
                ret[0].add(columna);
                ret[1].add(getValue(nodo.getChildAt(2)));
            }
            else{
                ret = null;
            }
        }
        return ret;
    }
    
    /**
     * Revisa un nodo Insert.
     * 
     * @param nodo El nodo a revisar (Debe ser un INSERT)
     * @return Devuelve true si se realizo la operacion correnctamente,
     * false de lo contrario.
     */
    private boolean revInsert(Nodo nodo){
        boolean ret = false;
        String id = getVarName(nodo.getChildAt(2));
        if (revTabla(nodo, id)){
            int index = 5;
            int[] orden = new int[gestor.getCurrent().getTable(id).getColumns().size()];
            if (nodo.getChildCount() == 10){
               index = 8;
               orden = ordenInsert(nodo.getChildAt(4), orden, id);
               if (orden[0] == -1)
                   return false;
            }
            else{
                for (int i = 0; i < orden.length; i++){
                    orden[i] = i;
                }
            }
            ArrayList<String> values = revValues(nodo.getChildAt(index), id, orden);
            if (values == null)
                return false;
            ArrayList<String> ordenado = new ArrayList<String>();
            for (int i = 0; i < orden.length; i++){
                ordenado.add("NULL");
            }
            for (int i = 0; i < orden.length; i++){
                int pos = orden[i];
                if (i < values.size()){
                    ordenado.remove(orden[i]);
                    if (ordenado.size() > pos){
                        ordenado.add(pos, values.get(i));
                    }
                    else{
                        ordenado.add(values.get(i));
                    }
                }
            }
            //gestor.getCurrent().getTable(id).insert(null, ordenado);
            gestor.insert(id, null, ordenado);
            ret = true;
        }
        return ret;
    }
    
    /**
     * Obtiene el orden en el que fueron especificadas las columnas a insertar.
     * 
     * @param nodo El nodo al que contiene todos los id's (IDLIST)
     * @param orden El array inicializado que contendra los numeros del orden
     * @param tabla La tabla a la que seran insertados los valores.
     * @return Devuelve un array de integers que contiene el orden en el cual
     * las columnas fueron especificadas.
     */
    private int[] ordenInsert(Nodo nodo, int[] orden, String tabla){
        String id;
        int cont = 0;
        while (nodo.toString().equals("IDLIST")){
            id = getVarName(nodo.getFirstChild());
            if (this.revColumn(nodo, tabla, id, true)){
                if(cont <= orden.length){
                    orden[cont] = gestor.getCurrent().getTable(tabla).getColNum(id);
                    cont ++;
                }
                else{
                    error("Se indicaron mas columnas que las que existen", nodo);
                    menosUnos(orden);
                }
            }
            else {
                menosUnos(orden);
            }
            nodo = nodo.getLastChild();
        }
        return orden;
    }
    
    /**
     * Llena un array de integers con menos unos
     * 
     * @param orden El array a llenar con menos unos.
     * @return devuelve el array lleno de menos unos.
     */
    private int[] menosUnos(int[] orden){
        for (int i = 0; i < orden.length; i++) {
            orden[i] = -1;
        }
        return orden;
    }
    
    /**
     * Revisa un nodo Values
     * 
     * @param nodo El nodo a revisar (Debe ser VALUES).
     * @param tabla La tabla a la que se insertaran los valores
     * @param orden El orden en el que se especificaron las columnas
     * @return Devuelve un ArrayList con los valores que seran insertados en las
     * columnas especificadas.
     */
    private ArrayList<String> revValues(Nodo nodo, String tabla, int[] orden){
        ArrayList<String> ret = new ArrayList<String>();
        String tipoCol = "";
        String tipoVal = "";
        String valor = "";
        int cont = 0;
        while (nodo.toString().equals("VALUES")){
            if (cont < orden.length){
                tipoCol = gestor.getCurrent().getTable(tabla).getCol(orden[cont]).getTipo();
                tipoVal = getValueType(nodo.getFirstChild());
                valor = getValue(nodo.getFirstChild());
                valor = componerTipos(tipoCol, tipoVal, nodo, valor);
                if (valor == null){
                    return null;
                }
                ret.add(valor);
                nodo = nodo.getLastChild();
                cont ++;
            }
            else{
                error("La tabla tiene menos columnas que la cantidad de valores a ingresar", nodo);
                return null;
            }
        }
        return ret;
    }
    
    /**
     * Obtiene el tipo del nodo Value especificado.
     * 
     * @param nodo El nodo VALUE del que se obtendra el tipo
     * @return Devuelve el tipo del nodo especificado.
     */
    private String getValueType(Nodo nodo){
        String res = "";
        switch (nodo.getFirstChild().toString()){
            case "INTLIT":
                res = "INT";
                break;
            case "FLOATLIT":
                res = "FLOAT";
                break;
            case "CHARLIT":
                String valor = getValue(nodo);
                boolean charl = true;
                if (valor.contains("-")){
                    valor = valor.substring(1, valor.length()-1);
                    String[] separado = valor.split("-");
                    if (separado.length == 3) {
                        if (separado[0].length() == 4 && separado[1].length() == 2 
                                && separado[2].length() == 2){
                            try{
                                Integer.parseInt(separado[0]);
                                Integer.parseInt(separado[1]);
                                Integer.parseInt(separado[2]);
                                charl = false;
                            }
                            catch (Exception e){
                                
                            }
                        }
                    }
                }
                if (charl) {
                    res = "CHAR";
                }
                else {
                    res = "DATE";
                }
                break;
            case "DATELIT":
                res = "DATE";
                break;
        }
        return res;
    }
    
    /**
     * Se intenta hacer la conversion de tipos, si es posible.
     * 
     * @param tipoCol El tipo de la columna, este es el tipo que no sera cambiado
     * @param tipoVal El tipo del valor, este es el tipo que va a ser cambiado
     * @param nodo El nodo que sera utilizado para indicar la fila y columna en caso
     * de error
     * @return Devuelve un String con el tipo compuesto o null si no se pudo hacer
     * el casting.
     */
    private String componerTiposT(String tipoCol, String tipoVal, Nodo nodo){
        boolean mal = false;
        if (tipoCol.equals(tipoVal)){
            return tipoVal;
        }
        else{
            switch(tipoVal){
                case "INT":
                    if (tipoCol.equals("CHAR")){
                        tipoVal = "CHAR";
                    }
                    else if (tipoCol.equals("FLOAT")){
                        tipoVal = "FLOAT";
                    }
                    else{
                        mal = true;
                    }
                    break;
                case "FLOAT":
                    if (tipoCol.equals("CHAR")){
                        tipoVal = "CHAR";
                    }
                    else if (tipoCol.equals("INT")){
                        tipoVal = "INT";
                    }
                    else{
                        mal = true;
                    }
                    break;
                case "DATE":
                    if (!tipoCol.equals("CHAR")) {
                        mal = true;
                    }
                    else{
                        tipoVal = "CHAR";
                    }
                    break;
                case "CHAR":
                    mal = true;
                    break;
            }
        }
        if (mal){
            tipoVal = null;
        }
        return tipoVal;
    }
    
    /**
     * Se intenta hacer la conversion de valores, si esta es posible
     * 
     * @param tipoCol El tipo de la columna, este es el tipo que no sera cambiado
     * @param tipoVal El tipo del valor, este es el tipo que va a ser cambiado
     * @param nodo El nodo que sera utilizado para indicar la fila y columna en caso
     * de error
     * @param valor El valor a ser casteado
     * @return Devuelve el valor casteado o null si no se pudo realizar el casteo.
     */
    private String componerTipos(String tipoCol, String tipoVal, Nodo nodo, String valor){
        boolean mal = false;
        if (tipoCol.equals(tipoVal)){
            return valor;
        }
        else{
            switch(tipoVal){
                case "INT":
                    if (tipoCol.equals("CHAR")){
                        valor = "'" + valor + "'";
                    }
                    else if (tipoCol.equals("FLOAT")){
                        valor = valor + ".0";
                    }
                    else{
                        mal = true;
                    }
                    break;
                case "FLOAT":
                    if (tipoCol.equals("CHAR")){
                        valor = "'" + valor + "'";
                    }
                    else if (tipoCol.equals("INT")){
                        String[] valores = valor.split("\\.");
                        valor = valores[0];
                    }
                    else{
                        mal = true;
                    }
                    break;
                case "DATE":
                    if (!tipoCol.equals("CHAR")) {
                        mal = true;
                    }
                    break;
                case "CHAR":
                    mal = true;
                    break;
            }
        }
        if (mal){
            error("Tipos incorrectos, no se puede convertir el tipo!!", nodo);
            valor = null;
        }
        return valor;
    }
    
    /**
     * Obtiene el valor de un nodo Value
     * 
     * @param nodo El nodo VALUE, del que se obtendra el valor
     * @return Devuelve un String con el valor.
     */
    private String getValue(Nodo nodo){
        String res;
        if (nodo.getFirstChild().toString().equals("\'")){
            res = "\'" + nodo.getFirstChild().getChildAt(1) + "\'";
        }
        else {
            String temp2 = nodo.getFirstChild().getFirstChild().toString();
            res = nodo.getFirstChild().getFirstChild().toString();
        }
        return res;
    }
    
    /**
     * Revisa un nodo Show.
     * 
     * @param nodo El nodo a revisar (Debe ser un SHOW).
     * @return Devuelve true si se pudo realizar la operacion o false si no se
     * pudo completar con exito.
     */
    private boolean revShow(Nodo nodo){
       boolean ret = false; 
       switch (nodo.getChildAt(1).toString()){
           case ("DATABASES"):
               // AQUI SHOW DATABASES
               mensaje(gestor.showDatabases());
               ret = true;
               break;
           case ("TABLES"):
               if (revCurrent(nodo)){
                    // AUI SHOW TABLES
                   mensaje(gestor.showTables());
                   ret = true;
               }
               break;
           case ("COLUMNS"):
               String id = getVarName(nodo.getLastChild());
               if (revTabla(nodo, id)){
                    // AQUI SHOW TABLE
                   mensaje(gestor.showTable(id));
                   ret = true;
               }
               break;
       }
       return ret;
    }
    
    /**
     * Revisa un nodo Drop.
     * 
     * @param nodo El nodo a revisar. (Debe ser DROP).
     * @return Devuelve true si se logra realizar la operacion o false
     * si la operacion no pudo ser realizada.
     */
    private boolean revDrop(Nodo nodo){
        String id = getVarName(nodo.getLastChild());
        if (isDatabase(nodo.getChildAt(1))){
            if(revBase(nodo, id)){
                gestor.dropDataBase(id);
                return true;
            }
        }
        else{
            if (revTabla(nodo, id)){
                gestor.dropTable(id);
                gestor.saveDataBase(gestor.getCurrent().getName());
                return true;
            }
        }
        return false;
    }
    
    /**
     * Revisa un nodo Use.
     * 
     * @param nodo El nodo a revisar (Debe ser USE).
     * @return Devuelve true si se logro realizar la operacion, de lo
     * contrario devuelve false.
     */
    private boolean revUse(Nodo nodo){
        boolean ret = false;
        String id = getVarName(nodo.getChildAt(2));
        if (revBase(nodo, id)) {
            gestor.setCurrent(id);
            gui.GUI.setCurrent(id);
            ret = true;
        }
        return ret;
    }
    
    /**
     * Revisa un nodo Alter.
     * 
     * @param nodo El nodo a revisar (Debe ser un ALTER).
     * @return Devuelve true si se logro realizar la operacion, de lo
     * contrario devuelve false.
     */
    private boolean revAlter(Nodo nodo){
        boolean ret = false;
        String id = getVarName(nodo.getChildAt(2));
        if (isDatabase(nodo.getChildAt(1))){
            String id2 = getVarName(nodo.getLastChild());
            if (revBase(nodo, id)){
                if (!gestor.checkExists(id2)){
                    // AQUI VA ALTER DATABASE
                    gestor.alterDatabase(id, id2);
                    ret = true;
                }
                else{
                    error("Ya existe una base de datos con el nombre indicado", nodo);
                    ret = false;
                }
            }
        }
        else{
            if (nodo.getChildCount() == 6){
                String id2 = getVarName(nodo.getLastChild());
                if (revTabla(nodo, id)){
                    if (gestor.getCurrent().checkExists(id)){
                        if (!gestor.getCurrent().checkExists(id2)){
                            // AQUI VA ALTER TABLE NAME
                            boolean cosa = gestor.alterTable(id, id2);
                            ret = true;
                        }
                        else{
                            error("Ya existe una tabla con el nombre indicado", nodo);
                            ret = false;
                        }
                    }
                }
                gestor.saveDataBase(gestor.getCurrent().getName());
            }
            else{
                ret = revAction(nodo.getChildAt(3), id);
                gestor.saveDataBase(gestor.getCurrent().getName());
            }
        }
        return ret;
    }
    
    /**
     * Revisa un nodo Action.
     * 
     * @param nodo El nodo a revisar (Debe ser un ACTION).
     * @param tabla La tabla sobre la que se realizara la accion-
     * @return Devuelve true si se logro realizar la operacion, de lo
     * contrario devuelve false.
     */
    private boolean revAction(Nodo nodo, String tabla){
        if (nodo.getFirstChild().toString().equals("ADD")){
            if (nodo.getChildAt(1).toString().equals("COLUMN")){
                String id = getVarName(nodo.getChildAt(2));
                String tipo = compTipo(nodo.getChildAt(3));
                if (!revColumn(nodo, tabla, id, false)){
                    gestor.getCurrent().getTable(tabla).addColumn(id, tipo);
                    if (nodo.getChildCount() == 5){
                        if(revConstraints(nodo.getChildAt(5), tabla)){
                            return true;
                        }
                        else{
                            gestor.getCurrent().getTable(tabla).dropColumn(id);
                            return false;
                        }
                    }
                    else
                        return true;
                }
            }
            else{
                return revConstraint(nodo.getChildAt(1), tabla);
            }
        }
        else{
            String id = getVarName(nodo.getLastChild());
            if (nodo.getChildAt(1).toString().equals("COLUMN")){
                if (revColumn(nodo, tabla, id, true)){
                    //gestor.getCurrent().getTable(tabla).dropColumn(id);
                    gestor.dropColumn(tabla, id);
                    return true;
                }
            }
            else{
                // Aqui va Drop Constraint
                if (gestor.getCurrent().getTable(tabla).getConstraints().containsKey(id)) {
                    gestor.dropConstraint(tabla, id);
                    return true;
                }
                else {
                    error("La constraint especificada no existe :(", nodo);
                }
            }
        }
        return false;
    }
    
    /**
     * Revisa si una columna ya ha sido creada o no.
     * 
     * @param nodo El nodo que contiene la fila y columna que se desplegaran en
     * el error, si se llega a necesitar.
     * @param tabla La tabla que contiene a la columna.
     * @param column La columna que se quiere saber si se encuentra.
     * @param creada Indica si se desea que la columna haya sido creada o no, para
     * tirar el error cuando sea necesario.
     * @return Devuelve true si se logro realizar la operacion, de lo
     * contrario devuelve false.
     */
    private boolean revColumn(Nodo nodo, String tabla, String column, boolean creada){
        if (revTabla(nodo, tabla)){
            if (gestor.getCurrent().getTable(tabla).checkExists(column)){
                if (!creada)
                    error("La columna ya ha sido creada", nodo);
                return true;
            }
            else {
                if (creada)
                    error("La columna especificada no existe", nodo);
                return false;
            }
        }
        return false;
    }
    
    /**
     * Revisa si una tabla ya ha sido creada en la Base de Datos current.
     * 
     * @param nodo El nodo que contiene la fila y columna que se desplegaran en
     * el error, si se llega a necesitar.
     * @param tabla La tabla de la que se desea saber si existe.
     * @return Devuelve true si se logro realizar la operacion, de lo
     * contrario devuelve false.
     */
    private boolean revTabla(Nodo nodo, String tabla){
        if (revCurrent(nodo)){
            if (gestor.getCurrent().checkExists(tabla)){
                return true;
            }
            else{
                error("La tabla indicada no existe", nodo);
                return false;
            }
        }
        else
            return false;
    }
    
    /**
     * Revisa que exista una base de datos.
     * 
     * @param nodo El nodo que contiene la fila y columna que se desplegaran en
     * el error, si se llega a necesitar.
     * @param base El nombre de la base de datos a revisar
     * @return Devuelve true si la base de datos existe y false si no existe
     */
    private boolean revBase(Nodo nodo, String base){
        if (gestor.checkExists(base)){
            return true;
        }
        else{
            error("La base de datos indicada no existe", nodo);
            return false;
        }
    }
    
    /**
     * Revisa que se este utilizando una base de datos.
     * 
     * @param nodo El nodo que contiene la fila y columna que se desplegaran en
     * el error, si se llega a necesitar.
     * @return Devuelve true si se esta utilizando una base de datos y false
     * si no se esta utilizando.
     */
    private boolean revCurrent(Nodo nodo){
        if (gestor.getCurrent() == null){
            error("No se esta utilizando una base de datos", nodo);
            return false;
        }
        else
            return true;
    }
    
    /**
     * Revisa un nodod Create.
     * 
     * @param nodo El nodo ha ser revisado (Debe ser un CREATE).
     * @return Devuelve true si se pudo realizar la operacion y false
     * si no se pudo realizar la operacion.
     */
    private boolean revCreate(Nodo nodo){
        String idlit = getVarName(nodo.getChildAt(2));
        if (isDatabase(nodo.getChildAt(1)))
            if(gestor.checkExists(idlit.toString())){
                error("La base de datos ya existe", nodo);
                return false;
            }
            else{
                // Se crea la base de datos
                gestor.createDataBase(idlit);
                return true;
            }
        else{
            if (gestor.getCurrent() != null) {
                if(gestor.getCurrent().checkExists(idlit)){
                    error("La tabla ya existe", nodo);
                    return false;
                }
                else{
                    // Se crea la tabla nueva
                    gestor.getCurrent().addTable(idlit.toString());
                    if (revIdTList(nodo.getChildAt(4), idlit)){
                        if (nodo.getChildCount() == 7){
                            boolean ret;
                            ret = revConstraints(nodo.getChildAt(5), idlit);
                            if (ret){
                                gestor.saveDataBase(gestor.getCurrent().getName());
                            }
                            else{
                                gestor.getCurrent().dropTable(idlit);
                            }
                            return ret;
                            
                        }
                        else{
                            gestor.saveDataBase(gestor.getCurrent().getName());
                            return true;
                        }
                    }
                    else{
                        gestor.getCurrent().dropTable(idlit);
                        return false;
                    }
                }
            }
            else {
                error("No se esta usando una base de datos", nodo);
            }
                return false;
        }
    }
    
    /**
     * Revisa un nodo Constraints
     * 
     * @param nodo El nodo a revisar (Debe ser CONSTRAINTS).
     * @param tabla La tabla en la que se colocara el constraint
     * @return Devuelve true si la operacion se completa y false en caso
     * contrario.
     */
    private boolean revConstraints(Nodo nodo, String tabla){
        boolean ret = true;
        while (nodo.toString().equals("CONSTRAINTS")){
            if (!revConstraint(nodo.getChildAt(0), tabla))
                ret = false;
            nodo = nodo.getLastChild();
        }        
        return ret;
    }
    
    /**
     * Revisa un nodo revConstraint
     * 
     * @param nodo El nodo a revisar (Debe ser Constraint).
     * @param tabla La tabla en la que se colocara el cosntraint.
     * @return Devuelve true si la operacion se completa y false en caso
     * contrario.
     */
    private boolean revConstraint(Nodo nodo, String tabla){
        boolean ret = true;
        ArrayList<String> cols;
        switch (nodo.getFirstChild().toString()){
            case "PRIMARY":
                cols = revIdList(nodo.getChildAt(4));
                for (String i : cols){
                    if (revColumn(nodo, tabla, i, true)) {
                        ret = gestor.addConstraint(tabla, getVarName(nodo.getChildAt(2)), i);
                    }
                    else {
                        gestor.dropConstraint(tabla, getVarName(nodo.getChildAt(2)));
                        error("La columna especificada no existe", nodo);
                        ret = false;
                    }
                }
                if (ret) {
                    gestor.getCurrent().getTable(tabla).setPrimary(true);
                }
                break;
            case "FOREIGN":
                cols = revIdList(nodo.getChildAt(4));
                ArrayList<String> colsR = revIdList(nodo.getChildAt(9));
                for (String i : cols){
                    int pos = cols.indexOf(i);
                    if (revColumn(nodo, tabla, i, true) && 
                            revColumn(nodo, getVarName(nodo.getChildAt(7)), colsR.get(pos), true)){
                        ret = gestor.addConstraint(tabla, getVarName(nodo.getChildAt(2)),
                                i, getVarName(nodo.getChildAt(7)), colsR.get(pos));
                    }
                    else{
                        gestor.dropConstraint(tabla, getVarName(nodo.getChildAt(2)));
                        error("La columna especificada no existe", nodo);
                        ret = false;
                    }
                }
                break;
            case "CHECK":
                Stack<String>[] exp = revExp(nodo.getChildAt(3));
                Collections.reverse(exp[0]);
                Collections.reverse(exp[1]);
                if (exp != null) {
                    ret = gestor.addConstraint(tabla, getVarName(nodo.getChildAt(1)), exp);
                }
                else {
                    error("Operaciones invalidas en el check :(", nodo);
                    ret = false;
                }
                break;
        }
        return ret;
    }
    
    /**
     * Revisa un nodo IdTList;
     * 
     * @param nodo El nodo a revisar (Debe ser un IDTLIST).
     * @param tabla La tabla que contendra las columnas especificadas
     * en la lista de ids
     * @return Devuelve true si la operacion se completa y false en caso
     * contrario.
     */
    private boolean revIdTList(Nodo nodo, String tabla){
        boolean ret = true;
        while (nodo.toString().equals("IDTLIST")){
            Nodo idmember = nodo.getFirstChild();
            Nodo tipo = idmember.getChildAt(1);
            String nombre = getVarName(idmember.getChildAt(0));
            if (!gestor.getCurrent().getTable(tabla).checkExists(nombre))
                gestor.getCurrent().getTable(tabla).addColumn(nombre, compTipo(tipo));
            else{
                ret = false;
                error("La columna fue especificada más de una vez", nodo);
            }
            nodo = nodo.getLastChild();
        }
        return ret;
    }
    
    /**
     * Obtiene el nombre el id de un Nodo Idlit.
     * 
     * @param idlit El nodo del que se obtendra el nombre.
     * @return Devuelve el id que se encuentrar dentro del IDLIT.
     */
    private String getVarName(Nodo idlit){
        return idlit.getLastChild().toString();
    }
    
    /**
     * Obtiene el tipo de un nodo Tipo.
     * 
     * @param tipo El nodo tipo.
     * @return Devuelve un String con el tipo.
     */
    private String compTipo(Nodo tipo){
        return tipo.getFirstChild().toString();
    }
    
    /**
     * Muestra un error en el GUI
     * 
     * @param error El texto que tendra el error.
     * @param nodo El nodo que contiene la fila y columna en las que ocurrio
     * el error.
     */
    private void error(String error, Nodo nodo){
        gui.GUI.error(error + ": Fila: " + nodo.getFila() + " Columna: " + nodo.getColumna());
    }
    
    /**
     * Muestra un mensaje en la GUI.
     * 
     * @param mensaje El mensaje a ser mostrado
     */
    private void mensaje(String mensaje){
        gui.GUI.error(mensaje);
    }
    
    /**
     * Revisa si El nodo espesificado es DATaBASE o no
     * 
     * @param nodo El nodo que sera chequeado
     * @return Devuelve true si es database y false si no lo es.
     */
    private boolean isDatabase(Nodo nodo){
        if (nodo.toString().equals("DATABASE"))
            return true;
        return false;
    }
    
    /**
     * Revisa un nodo Exp.
     * 
     * @param nodo El nodo a ser revisado (Debe ser EXP).
     * @return Devuelve un Array de Stacks cuyo primer elemento son los operandos
     * y su segundo elemento son los tipos de dichos operandos.
     */
    private Stack<String>[] revExp(Nodo nodo){
        Stack<String> stack = new Stack<String>();
        Stack<String> fin = new Stack<String>();
        Stack<String> tiposS = new Stack<String>();
        Stack<String> tiposF = new Stack<String>();
        Stack<String>[] res = new Stack[4];
        res[0] = stack;
        res[1] = fin;
        res[2] = tiposS;
        res[3] = tiposF;
        int num = 0;
        while (nodo.getChildCount() > 4){
            if (nodo.getChildCount() == 6){
                res = add(nodo.getChildAt(0), res);
                num = 1;
            }
            else {
                num = 0;
            }
            res = add(nodo.getChildAt(num), res);
            res = add(nodo.getChildAt(num+1), res);
            res = add(nodo.getChildAt(num+2), res);
            res = add(nodo.getChildAt(num+3), res);
            nodo = nodo.getLastChild();
        }
        if (nodo.getChildCount() == 4){
            res = add(nodo.getChildAt(0), res);
            num = 1;
        }
        else {
            num = 0;
        }
        res = add(nodo.getChildAt(num), res);
        res =  add(nodo.getChildAt(num+2), res);
        res =  add(nodo.getChildAt(num+1), res);
        while (!stack.isEmpty()){
            fin.push(stack.pop());
            tiposF.push(tiposS.pop());
        }
        Stack<String>[] ret = new Stack[2];
        ret[0] = fin;
        ret[1] = tiposF;
        return ret;
    }
    
    /**
     * Inserta un elemento en el stack postfix
     * 
     * @param nodo El nodo que se quiere insertar en el stack.
     * @param res El stack donde se insertara el nodo.
     * @return Devuelve el stack con el nodo insertado.
     */
    private Stack<String>[] add(Nodo nodo, Stack<String>[] res){
        Stack<String> stack = res[0];
        Stack<String> fin = res[1];
        Stack<String> tiposS = res[2];
        Stack<String> tiposF = res[3];
        String str;
        if (nodo.toString().equals("VALUE")){
            str = getValue(nodo);
        }
        else if (nodo.toString().equals("NOT")){
            str = nodo.toString();
        }
        else {
            str = getVarName(nodo);
        }
        int numA = jerarquia(str);
        if (numA != -1){
            if (!stack.empty()){
                int numB = jerarquia(stack.peek());
                if (numA > numB) {
                    stack.push(str);
                    if(numA == 0) {
                        tiposS.push("BOOLEAN");
                    }
                    else {
                        tiposS.push("OTRO");
                    }
                }
                else{
                    fin.push(stack.pop());
                    tiposF.push(tiposS.pop());
                    add(nodo, res);
                }        
            }   
            else {
                stack.push(str);
                if(numA == 0) {
                    tiposS.push("BOOLEAN");
                }
                else {
                    tiposS.push("OTRO");
                }
            }
        }
        else {
            fin.push(str);
            if (nodo.toString().equals("IDLIT")){
                tiposF.push("COLUMNA");
            }
            else{
                tiposF.push(this.getValueType(nodo));
            }
        }
        res[0] = stack;
        res[1] = fin;
        return res;
    }
    
    /**
     * Devuelve la jerarquia de un operador.
     * 
     * @param str El operador al que se le desea chequear la jerarquia
     * @return Devuelve la jerarquia del operador.
     */
    private int jerarquia(String str){
        int num = -1;
        switch (str){
            case "<":
                num = 1;
                break;
            case ">":
                num = 1;
                break;
            case "<=":
                num = 1;
                break;
            case ">=":
                num = 1;
                break;
            case "=":
                num = 1;
                break;
            case "<>":
                num = 1;
                break;
            case "AND":
                num = 0;
                break;
            case "OR":
                num = 0;
                break;
            case "NOT":
                num = 0;
                break;
        }
        return num;
    }
}
